(function(global) {

	// shim for IE < 9
	if (!Array.prototype.forEach) {
		Array.prototype.forEach = function forEach(iterator, context) {
			for (var i = 0, len = this.length; i < len; i++) {
				iterator.call(context, this[i], i, this);
			}
		};
	}
	Array.prototype.each = function each(iterator, context) {
		try {
			this.forEach(iterator, context);
		}
		catch (e) {
			if (e !== ProtoLove.$break) {
				throw e;
			}
		}
		return this;
	};

	Array.prototype.first = function first() {
		return this[0];
	};

	Array.prototype.first = function last() {
		return this[this.length - 1];
	};

	Array.prototype.include = function include(v) {
		return this.indexOf(v) > -1;
	};

	Array.prototype.without = function without(v) {
		var idx = -1, values = Array.prototype.slicle.call(this);
		while (1) {
			idx = values.indexOf(v);
			if (idx == -1) {
				break;
			}
			values.splice(idx, 1);
		}
		return values;
	};
	Array.prototype.reverse = function reverse() {
		var pos = this.length, i = 0, values = Array(pos);
		while (pos--) {
			values[i++] = this[pos];
		}
		return values;
	};
	Array.prototype.clone = function clone() {
		return this.slice();
	};

	Array.prototype.uniq = function uniq() {
		var unique = [], i = 0;
		this.forEach(function(value) {
			if (unique.indexOf(value) == -1) {
				unique[i++] = value;
			}
		});
		return unique;
	};
	Array.prototype.unique = Array.prototype.uniq;

	/**
	 * Call a method on every member of the array
	 *
	 * @param {String} method  The name of the method to call
	 * @param {Mixed} [argument1]  (Argument to pass to the method)
	 * @param {Mixed} [argument2]  (Argument to pass to the method)
	 * @param {Mixed} [argumentN]  (Argument to pass to the method)
	 * @return {pulp.Array}  This object
	 * @chainable
	 */
	Array.prototype.invoke = function invoke(method) {
		var args = Array.prototype.slice.call(arguments, 1);
		this.each(function(o) {
			Function.prototype.apply.call(o[method], o, args);
		});
		return this;
	};

	Array.prototype.inject = function inject(memo, iterator, context) {
		this.each(function(value, index) {
			memo = iterator.call(context, memo, value, index);
		});
		return memo;
	};

	Array.prototype.reject = function reject(iterator, context) {
		var results = [];
		this.each(function(value, index) {
			if (!iterator.call(context, value, index))
				results.push(value);
		});
		return results;
	};

	Array.prototype.minNumber = function minNumber() {
		return Math.min.apply(null, this);
	};
	Array.prototype.maxNumber = function maxNumber() {
		return Math.max.apply(null, this);
	};

	// shim: some browsers
	if (!Array.isArray) {
		Array.isArray = function isArray(o) {
			return Object.prototype.toString.call(o) == '[object Array]';
		};
	}
	// shims: IE < 9
	// From Prototype 1.6.1
	if (!Array.prototype.indexOf) {
		Array.prototype.indexOf = function indexOf(item, i) {
			i || (i = 0);
			var length = this.length;
			if (i < 0) { i = length + i; }
			for (; i < length; i++) {
				if (this[i] === item) { return i; }
			}
			return -1;
		};
	}
	// From Prototype 1.6.1
	if (!Array.prototype.lastIndexOf) {
		Array.prototype.lastIndexOf = function lastIndexOf(item, i) {
			i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1;
			var n = this.slice(0, i).reverse().indexOf(item);
			return (n < 0) ? n : i - n - 1;
		};
	}

	if (!Array.prototype.every) {
		Array.prototype.every = function every(iterator, context) {
			try {
				this.forEach(function(value, i) {
					if (iterator.call(context, value, i)) {
						return;
					}
					throw ProtoLove.$break;
				});
			}
			catch (e) {
				if (e == ProtoLove.$break) {
					return false;
				}
				else {
					throw e;
				}
			}
			return true;
		};
	}
	if (!Array.prototype.some) {
		Array.prototype.some = function some(iterator, context) {
			try {
				this.forEach(function(value, i) {
					if (iterator.call(context, value, i)) {
						return;
					}
					throw ProtoLove.$break;
				});
			}
			catch (e) {
				if (e == ProtoLove.$break) {
					return true;
				}
				else {
					throw e;
				}
			}
			return false;
		};
	}
	if (!Array.prototype.map) {
		function K(v) { return v; }
		Array.prototype.map = function map(iterator, context) {
			iterator = iterator || K;
			var results = [], i = 0;
			this.forEach(function(value, index) {
				results[i++] = iterator.call(context, value, index);
			});
			return results;
		}
	}
	if (!Array.prototype.filter) {
		Array.prototype.filter = function filter(iterator, context) {
			var results = [], i = 0;
			this.each(function(value, index) {
				if (iterator.call(context, value, index)) {
					results[i++] = value;
				}
			});
			return results;
		}
	}
	// from http://www.tutorialspoint.com/javascript/array_reduce.htm
	if (!Array.prototype.reduce) {
		Array.prototype.reduce = function reduce(fun /*, initial*/) {
			var len = this.length;
			if (typeof fun != "function") {
				throw new TypeError();
			}
			// no value to return if no initial value and an empty array
			if (len == 0 && arguments.length == 1) {
				throw new TypeError();
			}
			var i = 0;
			if (arguments.length >= 2) {
				var rv = arguments[1];
			}
			else {
				do {
					if (i in this) {
						rv = this[i++];
						break;
					}

					// if array contains no values, no initial value to return
					if (++i >= len) {
						throw new TypeError();
					}
				}
				while (true);
			}

			for (; i < len; i++) {
				if (i in this) {
					rv = fun.call(null, rv, this[i], i, this);
				}
			}

			return rv;
		};
	}
	// from http://www.tutorialspoint.com/javascript/array_reduceright.htm
	if (!Array.prototype.reduceRight) {
		Array.prototype.reduceRight = function reduceRight(fun /*, initial*/) {
			var len = this.length;
			if (typeof fun != "function") {
				throw new TypeError();
			}
			// no value to return if no initial value, empty array
			if (len == 0 && arguments.length == 1) {
				throw new TypeError();
			}
			var i = len - 1;
			if (arguments.length >= 2) {
				var rv = arguments[1];
			}
			else {
				do {
					if (i in this) {
						rv = this[i--];
						break;
					}

					// if array contains no values, no initial value to return
					if (--i < 0) {
						throw new TypeError();
					}
				}
				while (true);
			}

			for (; i >= 0; i--) {
				if (i in this) {
					rv = fun.call(null, rv, this[i], i, this);
				}
			}

			return rv;
		};
	}
	// from Prototype 1.6.1
	Array.from = function from(iterable) {
		if (!iterable) {
			return [];
		}
		if (typeof iterable.toArray == 'function') {
			return iterable.toArray();
		}
		var length = iterable.length || 0, results = new Array(length);
		while (length--) {
			results[length] = iterable[length];
		}
		return results;
	};

	// create ProtoLove if not exists
	global.ProtoLove = global.ProtoLove || {};
	// export $break
	global.ProtoLove.$break = global.$break || global.ProtoLove.$break || {};
	global.$break = global.ProtoLove.$break;
	// export $A
	global.ProtoLove.$A = Array.from;
	global.$A = global.$A || Array.from;

	var forEach = null,
		each = null,
		first = null,
		last = null,
		include = null,
		without = null,
		reverse = null,
		clone = null,
		uniq = null,
		invoke = null,
		reject = null,
		minNumber = null,
		maxNumber = null,
		isArray = null,
		indexOf = null,
		lastIndexOf = null,
		every = null,
		some = null,
		map = null,
		filter = null,
		reduce = null,
		reduceRight = null,
		from = null;

})(this);
